/**
 * Jan 28, 2009
 * @author abhi.sanoujam
 */
package sample;

import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Main {

  private final int numParties;
  
  // @Root
  private final Counter counter;
  // @Root
  private final Set<String> runners = new HashSet<String>();
  // @Root
  private final CyclicBarrier barrier;
  private final Thread[] parties;

  public Main(int numParties, int maxCounterValue) {
    this.numParties = numParties;
    counter = new Counter(numParties, maxCounterValue);
    barrier = new CyclicBarrier(numParties + 1);
    parties = new Thread[numParties];

    for (int i = 0; i < numParties; i++) {
      parties[i] = new Thread(new OddEvenRunnable(i, counter, barrier), getThreadName(i));
    }
  }

  private void runInSingleJvm() throws BrokenBarrierException, InterruptedException {
    // start all the counting parties
    for (int i = 0; i < numParties; i++) {
      parties[i].start();
    }
    startCounting();
  }

  private void startCounting() throws InterruptedException, BrokenBarrierException {
    System.out.println(Thread.currentThread().getName() + ": Sleeping for 1 secs....");
    Thread.sleep(1000);
    System.out.println(Thread.currentThread().getName() + ": ... And letting all the counting threads go!!");
    // let thy parties proceed
    barrier.await();
  }

  private String getThreadName(int partyNum) {
    String prefix = "";
    for (int i = 0; i < partyNum; i++) {
      prefix += "  ";
    }
    return prefix + "Party-" + partyNum;
  }

  public static void main(String[] args) throws Exception {
    Main main = new Main(2, 30);
    if (args.length == 0) {
      // run in a single node/jvm
      main.runInSingleJvm();
    } else {
      if (args.length != 1) {
        printUsageAndExit();
      }
      if ("odd".equals(args[0])) {
        main.startFirstThread();
      } else if ("even".equals(args[0])) {
        main.startSecondThread();
      } else if ("main".equals(args[0])) {
        main.startMainThread();
      } else if ("reset".equals(args[0])) {
        main.reset();
      } else
        printUsageAndExit();
    }

  }

  private void reset() {
    for (int i = 0; i < numParties; i++) {
      parties[i] = new Thread(new OddEvenRunnable(i, counter, barrier), getThreadName(i));
    }
    synchronized (runners) {
      this.runners.clear();
      counter.setValue(0);
    }
    System.out.println("Reset Done.");
  }

  private void startMainThread() throws Exception {
    if (runners.size() != 2) {
      System.out.println("Make sure that you have started both the odd and even threads.");
      printUsageAndExit();
    }
    synchronized (runners) {
      runners.add("main");
    }
    startCounting();
  }

  private void startSecondThread() {
    if (runners.contains("even")) {
      System.out.println("You have already started the even-printing thread.");
      printUsageAndExit();
    }
    synchronized (runners) {
      runners.add("even");
    }
    parties[1].start();
    System.out.println("Started even thread");
  }

  private void startFirstThread() {
    if (runners.contains("odd")) {
      System.out.println("You have already started the odd-printing thread.");
      printUsageAndExit();
    }
    synchronized (runners) {
      runners.add("odd");
    }
    parties[0].start();
    System.out.println("Started odd thread");
  }

  private static void printUsageAndExit() {
    System.out.println("USAGE: java Main [odd | even | main | reset]");
    System.out.println("   No-arguments - Starts 2 threads printing odd and even values in single jvm.");
    System.out.println("   odd - starts the odd-number printing thread in this node.");
    System.out.println("   even - starts the even-number printing thread in this node.");
    System.out.println("   main - starts a thread which lets the odd and even threads go ahead.");
    System.out.println("   reset - Resets all states so you can start all over again.");
    System.exit(1);
  }

}
